/**
 Created by:
 muhtearjan mehmood مۇختەرجان مەخمۇت (Email: yeganaaa@163.com)
 At 2020-01-16 2:39 چۈشتىن كېيىن
 */
import {IRpcSocketServer, RpcBridgeHost} from "rpc_dream";
import {v4 as uuid} from "uuid";

enum MessageType {
    requestConnect = 0x01,
    connectSuccessfully = 0x02,
    error = 0x04,
    requestClose = 0x05,
    closeOK = 0x06,
    generalMessageRequest = 0x03,
    generalMessageResponse = 0x07,
}

class Message {
    constructor(public ownerClient: string, public serverId: string, public data: any, public type: MessageType) {
    }
}

function validateMessage(obj: Message) {
    let valid = typeof (obj.serverId) as any == "string"
    valid = valid && (obj.type == MessageType.generalMessageRequest || obj.type == MessageType.generalMessageResponse || obj.type == MessageType.requestConnect || obj.type == MessageType.connectSuccessfully)
    valid = valid && obj.data != null
    return valid
}

export function iFrameWindowFinderById(clientId: string) {
    const iframe = document.getElementById(clientId) as HTMLIFrameElement
    if (iframe == null) {
        throw new Error(`Client iframe with id: '${clientId}' is not found id html document, maybe partner client id is not correct.`)
    }
    return iframe.contentWindow!
}

export class BrowserHostedRpcServer implements IRpcSocketServer<Window> {
    onClientConnected: (client: RpcBridgeHost<any>) => void = null!
    onError: (error: Error) => void = null!
    onLaunch: () => void = null!
    onShutDown: () => void = null!
    public server: Window = window

    public constructor(public serverId: string, private clientsWindowFinder: (clientId: string) => Window = iFrameWindowFinderById) {

    }
    async prepareForLaunch(): Promise<void> {
        window.addEventListener("message", this.listener)
        setTimeout(() => {
            this.onLaunch()
        }, 0)
    }
    private listener = (message: MessageEvent) => {
        try {
            const obj = message.data as Message || {}
            // console.log(obj)
            let valid = validateMessage(obj)
            valid = valid && this.serverId == obj.serverId
            if (!valid) return
            const clientWindow = this.clientsWindowFinder(obj.ownerClient)
            if (obj.type == MessageType.requestConnect) {
                clientWindow.postMessage(new Message(obj.ownerClient, this.serverId, {}, MessageType.connectSuccessfully), "*")
                const host = new BrowserHostedRpcBridgeHost(clientWindow, obj.ownerClient, this.serverId, BridgeMode.Server)
                this.onClientConnected(host)
                // host.onConnected()
                return;
            }
            // if (obj.type == MessageType.generalMessageResponse || obj.type == MessageType.generalMessageRequest) {
            //     //Do nothing, because incoming message handled by BrowserHostedRpcBridgeHost
            // }
            if (obj.type == MessageType.requestClose) {
                clientWindow.postMessage(new Message(obj.ownerClient, this.serverId, {}, MessageType.closeOK), "*")
            }
        } catch (e) {
            console.error(e)
        }
    }
    async shutdown(): Promise<void> {
        window.removeEventListener("message", this.listener)
    }
}

export enum BridgeMode {
    Server = 0x01,
    Client = 0x02
}

export class BrowserHostedRpcBridgeHost extends RpcBridgeHost<Window> {
    private _closed = false

    constructor(public socket: Window, private clientId: string, public serverId: string, private clientMode: BridgeMode = BridgeMode.Client) {
        super();
    }
    initialize(): Promise<void> {
        return new Promise<void>((resolve, reject) => {
            let taskId: any = null!
            if (this.clientMode == BridgeMode.Client) {
                taskId = setTimeout(() => {
                    reject(this.onError(new Error(`Connect to server timeout, this is maybe because this client id: ${this.clientId} is incorrect so partner can't find correspond iframe, or partner not ready yet at the time.`)))
                }, 5000)
            }
            const handler = (message: MessageEvent) => {
                try {
                    const obj = message.data as Message
                    let valid = validateMessage(obj)
                    valid = valid && obj.ownerClient == this.clientId
                    valid = valid && obj.serverId == this.serverId
                    valid = valid && !this._closed
                    if (!valid) return
                    if (obj.type == MessageType.connectSuccessfully) {
                        this.onConnected()
                        clearTimeout(taskId)
                    }
                    valid = valid && this.clientMode == BridgeMode.Client
                    valid = valid && obj.type == MessageType.generalMessageResponse
                    if (valid) {
                        this.onMessageReceived(new TextEncoder().encode(JSON.stringify(obj.data)))
                    }
                    valid = true
                    valid = valid && this.clientMode == BridgeMode.Server
                    valid = valid && obj.type == MessageType.generalMessageRequest
                    if (valid) {
                        this.onMessageReceived(new TextEncoder().encode(JSON.stringify(obj.data)))
                    }
                    if (obj.type == MessageType.requestClose) {
                        this.socket.postMessage(new Message(this.clientId, this.serverId, {}, MessageType.closeOK), "*")
                        this._closed = true
                        this.onClosed()
                    }
                    if (obj.type == MessageType.closeOK) {
                        this.socket.removeEventListener("message", handler)
                        this._closed = true
                        this.onClosed()
                        window.removeEventListener("message", handler)
                    }
                } catch (e) {
                    console.error(e)
                }
            }
            window.addEventListener("message", handler)

            const message = new Message(this.clientId, this.serverId, {}, MessageType.requestConnect)
            this.socket.postMessage(message, "*")
            if (this.clientMode == BridgeMode.Server) {
                setTimeout(() => this.onConnected(), 0)
            }
            resolve()
        })
    }

    close(callback: () => void): void {
        this.socket.postMessage(new Message(this.clientId, this.serverId, {}, MessageType.requestClose), "*")
    }

    send(data: Uint8Array, callback: (error: Error) => void): void {
        this.socket.postMessage(new Message(this.clientId, this.serverId, JSON.parse(new TextDecoder().decode(data)), this.clientMode == BridgeMode.Client ? MessageType.generalMessageRequest : MessageType.generalMessageResponse), "*")
    }
}
